package copyengine.ui.component.scrollbar
{
	import copyengine.ui.component.CEUISprite;
	import copyengine.ui.component.button.CEButton;
	import copyengine.utils.GeneralUtils;
	import copyengine.utils.tick.GlobalTick;

	import flash.display.DisplayObjectContainer;
	import flash.events.MouseEvent;

	public class CEScrollBarCore extends CEUISprite
	{
		/**
		 *define the minimun size of the thumb , the thumb will autoScale by the track size.
		 * but thie thumb can't be smaller than this value.
		 */
		private static const MINIMUM_THUMB_SIZE:Number = 30;

		/**
		 *when user  drage the thumb , it will move with user's mouse. but if mouse.stageX/mouse.stageY
		 * is out of this range then scrollBar will stop change automate.
		 */
		private static const THUMB_REACT_RANGE:Number = 50;


		public static const LAYOUT_HORIZONTAL:String = "horizontal";
		public static const LAYOUT_VERTICAL:String = "vertical";
		public static const LAYOUT_AUTO:String = "auto";

		/**
		 * set the scrollBar layout direction , this property should be one of the three value
		 * 1) horizontal
		 * 2) vertical
		 * 3) auto    auton means the class will detect the layout for the user . the detect rule base on it width/height size.
		 */
		private var direction:String;

		/**
		 *  scrollBar suport six button to operate
		 *
		 *  1) scrollToStart , scrollToEnd                  set scrollPosition to minScrollPosition/maxScrollPosition
		 *  2) scrollNext , scrollPrev						  set scrollPosition to scrollPositon +/-  lineScrollSize
		 *  3) scrollNextPage , scrollPrevPage		  set scrollPosition to scrollPosition +/- pageScrollSize
		 *
		 * pageScrollSize also will trigger when user click the track
		 *
		 */
		private var lineScrollSize:Number = 0;
		private var pageScrollSize:Number = 0;

		/**
		 *  scrollBar will scroll during minScrollPosition~maxScrollPosition. normally
		 * minScrollPosition = 0 and maxScrollPosition = host target maxScrollPosition.
		 */
		private var minScrollPosition:Number = 0;
		private var maxScrollPosition:Number = 0;

		/**
		 * previous scrollPosition
		 */
		private var oldScrollPosition:Number = 0;
		/**
		 * current scroll scrollBar scrollPosition
		 */
		private var _scrollPosition:Number = 0;

		/**
		 * define the thumb skin
		 */
		private var thumb:CEButton

		/**
		 * define the track skin
		 */
		private var track:CEButton;

		/**
		 * when user start drag the thumb , the offest between mousePos to top left corner.
		 */
		private var thumbScrollOffset:Number = 0;

		/**
		 *是否根据滚动区域的大小自动缩放Thumb
		 */
		private var isAutoScaleThumb:Boolean;

		public function CEScrollBarCore(_targetMc:DisplayObjectContainer,
										_thumb:CEButton,
										_track:CEButton,
										_direction:String = LAYOUT_AUTO,
										_isAutoScaleThumb:Boolean = true)
		{
			super(_targetMc);
			thumb = _thumb;
			track = _track;
			direction = _direction;
			isAutoScaleThumb = _isAutoScaleThumb;
		}

		public function set enabled(value:Boolean):void
		{
			thumb.visible = mouseEnabled = mouseChildren = value;
		}

		public function get enabled():Boolean
		{
			return thumb.visible;
		}

		public function getDirection():String
		{
			return this.direction;
		}

		public function initializeScrollBar(_lineScrollSize:Number, _pageScrollSize:Number,
											_minScrollPosition:Number, _maxScrollPosition:Number):void
		{
			lineScrollSize = _lineScrollSize;
			pageScrollSize = _pageScrollSize;

			minScrollPosition = _minScrollPosition;
			maxScrollPosition = _maxScrollPosition;

			calculateScrollBarDirection();
			calculateThumbSize();
			initializeScrollBarSkin();
			addListener();
		}

		public function refreshScrollBar(_lineScrollSize:Number, _pageScrollSize:Number,
										 _minScrollPosition:Number, _maxScrollPosition:Number, _newStartScrollPosition:int):void
		{
			lineScrollSize = _lineScrollSize;
			pageScrollSize = _pageScrollSize;

			minScrollPosition = _minScrollPosition;
			maxScrollPosition = _maxScrollPosition;

			enabled = maxScrollPosition > 0;

			calculateScrollBarDirection();
			calculateThumbSize();
			initializeScrollBarSkin();
			updateScrollPosition(false, _newStartScrollPosition);
//			scrollPosition = _newStartScrollPosition
		}

		override protected function doDispose():void
		{
			removeListener();
			releaseThumb();
			releaseTrack();
			GeneralUtils.removeTargetFromParent(thumb);
			GeneralUtils.removeTargetFromParent(track);
		}

		public function get scrollPosition():Number
		{
			return _scrollPosition;
		}

		public function set scrollPosition(_value:Number):void
		{
			if (_value != _scrollPosition)
			{
				updateScrollPosition(false, _value);
			}
		}

		//================
		//== Private Function
		//================

		/**
		 * if scrollBar direction set to LAYOUT_AUTO , this calculate the layout direction
		 * by it width/height size , otherwise do nothing.
		 */
		private function calculateScrollBarDirection():void
		{
			if (direction == LAYOUT_AUTO)
			{
				if (track.width > track.height)
				{
					direction = LAYOUT_HORIZONTAL;
				}
				else
				{
					direction = LAYOUT_VERTICAL;
				}
			}
		}

		/**
		 * calculate the thumb size base on scroll property
		 */
		private function calculateThumbSize():void
		{
			if (isAutoScaleThumb)
			{
				if (direction == LAYOUT_HORIZONTAL)
				{
					// each time scroll we expect is scroll 1% , if can't then set the thumb to the minmun size.
					thumb.width = Math.max(MINIMUM_THUMB_SIZE, track.width - maxScrollPosition + minScrollPosition);
				}
				else // layout == LAYOUT_VERTICAL
				{
					thumb.height = Math.max(MINIMUM_THUMB_SIZE, track.width - maxScrollPosition + minScrollPosition);
				}
			}
		}

		/**
		 * initialize the scrollBar skin
		 */
		private function initializeScrollBarSkin():void
		{
			if (direction == LAYOUT_HORIZONTAL)
			{
				thumb.y = (track.height - thumb.height) >> 1;
			}
			else // layout == LAYOUT_VERTICAL
			{
				thumb.x = (track.width - thumb.width) >> 1;
			}
		}

		private function addListener():void
		{
			GeneralUtils.addTargetEventListener(thumb, MouseEvent.MOUSE_DOWN, thumbOnMouseDown);
			GeneralUtils.addTargetEventListener(track, MouseEvent.MOUSE_DOWN, trackOnMouseDown);
		}

		private function removeListener():void
		{
			GeneralUtils.removeTargetEventListener(thumb, MouseEvent.MOUSE_DOWN, thumbOnMouseDown);
			GeneralUtils.removeTargetEventListener(track, MouseEvent.MOUSE_DOWN, trackOnMouseDown);

			GeneralUtils.removeTargetEventListener(stage, MouseEvent.MOUSE_UP, thumbOnMouseUp);
			GeneralUtils.removeTargetEventListener(stage, MouseEvent.MOUSE_MOVE, thumbOnMouseMove);
		}

		/**
		 * when user operate thumb or click track will trigger scrollPosition ,
		 * in this case need to send ScrollBarEvent.SCROLL ,
		 * otherwise the scrollPostion will change by set scrollPostion function by other CECompoment.
		 * then do not need to send ScrollEvent.
		 *
		 */
		private function updateScrollPosition(_isDispatchEvent:Boolean, _newScrollPosition:Number, _thumbPosition:Number = NaN):void
		{
			oldScrollPosition = _scrollPosition;
			_scrollPosition = _newScrollPosition;
			if (isNaN(_thumbPosition))
			{
				updateThumb(getThumbPositionByScrollPosition());
			}
			else
			{
				updateThumb(_thumbPosition);
			}
			if (_isDispatchEvent && hasEventListener(CEScrollBarCoreEvent.SCROLL))
			{
				this.dispatchEvent(new CEScrollBarCoreEvent(direction,
															_scrollPosition - oldScrollPosition,
															_scrollPosition, CEScrollBarCoreEvent.SCROLL));
			}
		}

		/**
		 * update UI thumb position
		 */
		private function updateThumb(_thumbPosition:Number):void
		{
			if (direction == LAYOUT_HORIZONTAL)
			{
				thumb.x = GeneralUtils.normalizingValue(_thumbPosition, 0, track.width - thumb.width);
			}
			else
			{
				thumb.y = GeneralUtils.normalizingValue(_thumbPosition, 0, track.height - thumb.height);
			}
		}

		private function getThumbPositionByScrollPosition():Number
		{
			if (direction == LAYOUT_HORIZONTAL)
			{
				return (_scrollPosition - minScrollPosition) / (maxScrollPosition - minScrollPosition) * (track.width - thumb.width);
			}
			else // layout == LAYOUT_VERTICAL
			{
				return (_scrollPosition - minScrollPosition) / (maxScrollPosition - minScrollPosition) * (track.height - thumb.height);
			}
		}

		//============
		//== Event Listener
		//============

		//=========
		//==Thumb
		//=========
		private function thumbOnMouseDown(e:MouseEvent):void
		{
			e.stopImmediatePropagation();
			if (direction == LAYOUT_HORIZONTAL)
			{
				thumbScrollOffset = e.localX * thumb.scaleX;
			}
			else // layout == LAYOUT_VERTICAL
			{
				thumbScrollOffset = e.localY * thumb.scaleY;
			}
			stage.addEventListener(MouseEvent.MOUSE_UP, thumbOnMouseUp, false, 0, true);
			stage.addEventListener(MouseEvent.MOUSE_MOVE, thumbOnMouseMove, false, 0, true);
		}

		/**
		 * when user drage thumb to move , it will check Is user mouse out of the range.
		 * if so , then will stop move action.
		 */
		private function thumbOnMouseMove(e:MouseEvent):void
		{
			var pos:Number;
			var posRange:Number;
			var newScrollPosition:Number;
			if (direction == LAYOUT_HORIZONTAL)
			{
				pos = GeneralUtils.normalizingValue(this.mouseX - track.x - thumbScrollOffset, 0, track.width - thumb.width);
				newScrollPosition = pos / (track.width - thumb.width) * (maxScrollPosition - minScrollPosition) + minScrollPosition;
//				posRange = Math.abs(e.stageY - track.y);
				posRange = this.mouseY;
			}
			else //loyout == LAYOUT_VERTICAL
			{
				pos = GeneralUtils.normalizingValue(this.mouseY - track.y - thumbScrollOffset, 0, track.height - thumb.height);
				newScrollPosition = pos / (track.height - thumb.height) * (maxScrollPosition - minScrollPosition) + minScrollPosition;

//				posRange = Math.abs(e.stageX - track.x)
				posRange = this.mouseX;
			}
			newScrollPosition = isNaN(newScrollPosition) ? 0 : newScrollPosition;
			updateScrollPosition(true, newScrollPosition, pos);
			if (posRange > THUMB_REACT_RANGE || posRange < -THUMB_REACT_RANGE)
			{
				releaseThumb();
			}
		}

		private function thumbOnMouseUp(e:MouseEvent):void
		{
			releaseThumb();
		}

		private function releaseThumb():void
		{
			GeneralUtils.removeTargetEventListener(stage, MouseEvent.MOUSE_UP, thumbOnMouseUp);
			GeneralUtils.removeTargetEventListener(stage, MouseEvent.MOUSE_MOVE, thumbOnMouseMove);
		}

		//========
		//== Track
		//========
		private var isTrackMoveLeftUP:Boolean // use to judgement the move direction

		private function trackOnMouseDown(e:MouseEvent):void
		{
			//when user click the track ,first need to calculate which way need to scroll to
			if (direction == LAYOUT_HORIZONTAL)
			{
				isTrackMoveLeftUP = this.mouseX - thumb.x < 0;
			}
			else //loyout == LAYOUT_VERTICAL
			{
				isTrackMoveLeftUP = this.mouseY - thumb.y < 0;
			}
			// move the thumb once when user click the track,
			// then delay an time count then autoRepeat call moveByClickTrack() function
			// until checkIsReleaseTrack function return false, or OnMouseUpFunction call.
			moveByClickTrack();
			GlobalTick.instance.callLaterAfterTimerCount(moveByClickTrack,
														 0.2, Math.floor((maxScrollPosition - minScrollPosition) / pageScrollSize));

			stage.addEventListener(MouseEvent.MOUSE_UP, trackOnMouseUp, false, 0, true);
		}

		/**
		 * call this function to move scrollPosition ,each time move pageScrollSize(or small that one.)
		 */
		private function moveByClickTrack():void
		{
			//first calculate current mouse scroll position.
			//if mouseScrollPosition is lager or small than _scrollPosition+pageScrollSize/_scrollPosition-pageScrollSize
			//then scroll to the _scrollPosition +- pageScrollSize
			//else just scroll to mousePosition
			var mousePosition:Number;
			var newScrollPosition:Number;
			if (direction == LAYOUT_HORIZONTAL)
			{
				mousePosition = this.mouseX / (track.width - thumb.width) * (maxScrollPosition - minScrollPosition) + minScrollPosition;
			}
			else //loyout == LAYOUT_VERTICAL
			{
				mousePosition = this.mouseY / (track.height - thumb.height) * (maxScrollPosition - minScrollPosition) + minScrollPosition;
			}

			if (_scrollPosition < mousePosition)
			{
				newScrollPosition = Math.min(mousePosition, _scrollPosition + pageScrollSize);
			}
			else if (_scrollPosition > mousePosition)
			{
				newScrollPosition = Math.max(mousePosition, _scrollPosition - pageScrollSize);
			}
			else
			{
				releaseTrack();
				return;
			}
			newScrollPosition = GeneralUtils.normalizingValue(newScrollPosition, minScrollPosition, maxScrollPosition);
			if (!checkIsReleaseTrack(newScrollPosition))
			{
				updateScrollPosition(true, newScrollPosition);
			}
		}

		/**
		 * check is can move to newScrollPostion
		 * 1` if user Mouse is RollOut Track
		 * 2` if thumb already under userMouse(means current scrollPosition will change the direction)
		 * during those situation , can't move the scrollPosition and stop Track move state.
		 */
		private function checkIsReleaseTrack(_newScrollPosition:Number):Boolean
		{
			// it mouse is moved out of the scrollbar then will release the track implemently
			if (this.mouseX < track.x || this.mouseX > (track.x + track.width)
				|| this.mouseY < track.y || this.mouseY > (track.y + track.height))
			{
				releaseTrack();
				return true;
			}
			if (isTrackMoveLeftUP)
			{
				// if the tack is move fore right to left down to up
				// and mouse already in the thumb. then release the track
				if ((direction == LAYOUT_HORIZONTAL && this.mouseX > thumb.x)
					|| (direction == LAYOUT_VERTICAL && this.mouseY > thumb.y))
				{
					releaseTrack();
					return true;
				}
			}
			else
			{
				if ((direction == LAYOUT_HORIZONTAL && this.mouseX < thumb.x + thumb.width)
					|| (direction == LAYOUT_VERTICAL && this.mouseY < thumb.y + thumb.height))
				{
					releaseTrack();
					return true;
				}
			}
			return false;
		}

		private function trackOnMouseUp(e:MouseEvent):void
		{
			releaseTrack();
		}

		private function releaseTrack():void
		{
			GeneralUtils.removeTargetEventListener(stage, MouseEvent.MOUSE_UP, thumbOnMouseUp);
			GlobalTick.instance.removeTickNodeByFunction(moveByClickTrack);
		}

	}
}
